#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>

#define SEP " "
#define NUM 1024
#define SIZE 128

//实现代码替换的一个小技巧
#define DROP_SPACE(s) do { while(isspace(*s)) s++; }while(0)

char command_line[NUM];
char* command_args[SIZE];

char env_buffer[NUM];

#define NONE_REDIR -1
#define INPUT_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2

//有两个global全局变量
int g_redir_flag = NONE_REDIR;
char* g_redir_filename = NULL;

extern char** environ;

//对应上层的内建命令
int ChangeDir(const char* new_path)
{
    chdir(new_path);

    return 0;//调用成功
}

void PutEnvInMyShell(char* new_env)
{
    putenv(new_env);
}

void CheckDir(char* commands)
{
    assert(commands);

    //[start, end)
    char* start = commands;

    //指向的是字符串最后的那个'\0' 
    char* end = commands + strlen(commands);

    //ls -a -l>log.txt
    while(start < end)
    {
        if(*start == '>')
        {
            if(*(start + 1) == '>')
            {
                //ls -a -l>>log.txt -- 追加
                *start = '\0';
                start += 2;
                g_redir_flag = APPEND_REDIR;
                DROP_SPACE(start);
                g_redir_filename = start;
                break;
            }
            else 
            {
                //ls -a -l > log.txt -- 输出重定向
                *start = '\0';
                start++;
                DROP_SPACE(start);
                g_redir_flag = OUTPUT_REDIR;
                g_redir_filename = start;
                break; 
            }
        }
        else if(*start == '<')
        {
            //输入重定向
            *start = '\0';
            start++;
            DROP_SPACE(start);
            g_redir_flag = INPUT_REDIR;
            g_redir_filename = start;
            break;
        }
        else 
        {
            start++;
        }
    }
}

int main()
{
    umask(2);
    //shell本质就是一个死循环
   
    while(1)
    {
        //每次执行前恢复至最开始
        g_redir_flag = NONE_REDIR;
        g_redir_filename = NULL;

        //不关心获取这些属性的接口，搜索一下都有
        
        //1.显示提示符
        printf("[用户名@我的主机名 当前目录]# ");
        fflush(stdout);

        //2.获取用户输入
        memset(command_line, '\0', sizeof(command_line));
        //从键盘获取，标准输入，stdin，获取到的是C风格的字符串(stdio.h结尾的)，'\0'结尾
        fgets(command_line, NUM, stdin);
        command_line[strlen(command_line) - 1] = '\0';//清空\n回车
        //printf("%s\n", command_line);

        //2.1 ls -a -l>log.txt or cat<file.txt or ls -a -l>>log.txt or ls -a -l
        //ls -a -l>log.txt -> ls -a -l\0log.txt
        CheckDir(command_line);

        //3. "ls -a -l -i" -> "ls" "-a" "-l" "-i" 字符串切分 -- 因为这些参数一定得以列表或者数组方式传递给程序替换接口
        //shell必须切分，因为必须调用execl函数
        //将第一个字符串地址用0号下标指向，第二个字符串地址用1号下标指向 
        command_args[0] = strtok(command_line, SEP);

        int index = 1;

        //给ls命令添加颜色: 如果提取出来的程序名是ls -- 1下标设置成改颜色的
        if(strcmp(command_args[0], "ls") == 0) command_args[index++] = (char*)"--color=auto";

        //strtok截取成功返回字符串起始地址
        //截取失败，返回NULL
        while(command_args[index++] = strtok(NULL, SEP));
        
        //for debug
        //int i = 0;
        //for(i = 0; i < index; i++)
        //{
        //    printf("%d : %s\n", i, command_args[i]);
        //}
        
        //4.TODO -- 编写后面的逻辑，内建命令(由父Shell自己实现的自己调用的一个函数)
        if(strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
        {
            //让调用方进行路径切换，父进程
            ChangeDir(command_args[1]);
            continue;
        }
        
        //走到这里一定是将命令行参数解析完了，包括命令 + 选项
        
        //将环境变量的信息导入在了父进程的上下文当中
        if(strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
        {
            //环境变量列表(是个指针数组，每个元素是个指针指向一个环境变量)
            //我们传的是一个字符串首地址，但是环境变量的内容还是我们自己维护的
            //目前，环境变量信息在comman_line，会被清空，那么环境变量当然就没有了
            //所以此处我们需要自己保存一下环境变量的内容
            
            strcpy(env_buffer, command_args[1]);
            PutEnvInMyShell(env_buffer);
            //PutEnvInMyShell(command_args[1]);//MYENV=112233
            continue;
        }

        //5.创建进程，执行
        //如果自己直接程序替换的话，就把自己写的shell给替换了
        
        pid_t id = fork();
        if(id == 0)
        {
            int fd = -1;
            switch(g_redir_flag)
            {
                case NONE_REDIR:
                    break;
                case INPUT_REDIR:
                    fd = open(g_redir_filename, O_RDONLY);
                    dup2(fd, 0);
                    break;
                case OUTPUT_REDIR:
                    fd = open(g_redir_filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                    dup2(fd, 1);
                    break;
                case APPEND_REDIR:
                    fd = open(g_redir_filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                    dup2(fd, 1);
                    break;
                default:
                    printf("Bug?\n");
                    break;
            }
            //子进程
            //6.程序替换
            //程序替换，会影响曾经子进程打开的文件吗？-- 不影响
            
            //execvpe(command_args[0], command_args, environ);
            execvp(command_args[0], command_args);

            exit(1);//执行到这里，子进程一定替换失败了
        }

        int status = 0;
        
        pid_t ret = waitpid(id, &status, 0);

        if(ret > 0)
        {
            printf("等待子进程成功: sig: %d, code: %d\n", status & 0x007F, (status & 0xFF00) >> 8);
        }

    }//end while

    return 0;
}
